var EXPORTED_SYMBOLS = ['DotoManager', 'DotoTask'];

var Ci = Components.interfaces;
var Cc = Components.classes;

var SQLITE_FILE = "doto_tasks.sqlite";
var SQLITE_SCHEMA = 
	"CREATE TABLE doto_tasks(" +
	"  id INTEGER PRIMARY KEY AUTOINCREMENT, " +
	"  title VARCHAR(256), " +
	"  flag INTEGER, " +
	"  duetime INTEGER)";

var gDotoDBConnection = null;
var dirService = Cc["@mozilla.org/file/directory_service;1"].getService(Ci.nsIProperties);
var storageService = Cc["@mozilla.org/storage/service;1"].getService(Ci.mozIStorageService);

var gDotoStarted = false;

var Application = Components.classes["@mozilla.org/fuel/application;1"]
                  .getService(Components.interfaces.fuelIApplication);

function DotoTask(id) {
	this.id = id;
	this.title = "";
	this.due = new Date(0);
	this.notified = false;
}

function _connectToDatabase() {
	if (!gDotoDBConnection) {
		var file = dirService.get("ProfD", Ci.nsIFile);
		file.append(SQLITE_FILE);
		gDotoDBConnection = DotoManager.openDatabase(file);
	}
	return gDotoDBConnection;
}

const FLAG_NEW = 0;
const FLAG_COMPLETE = 1;

var DotoManager = {
	tasks: new Array(),
	completedTasks: new Array(),
	timer: null,
	 
	init: function() {
		if (!gDotoStarted) {
			this.connection = _connectToDatabase();
			
			if (this.firstrun) {
				this.addTask("Try out Doto. Just input some task then hit enter in the box.", new Date(0));
			} else {
				var selSql = "SELECT id, title, duetime FROM doto_tasks WHERE flag == " + FLAG_NEW;
				var selStatement = this.createStatement(selSql);
				while (selStatement.executeStep()) {
					var id = selStatement.getInt32(0);
					var title = selStatement.getUTF8String(1);
					var duetime = selStatement.getInt64(2);

					var task = new DotoTask(id);
					task.title = title;
					task.due = new Date(duetime);
					this.tasks.push(task);
				}							
				
				var selSql = "SELECT id, title, duetime FROM doto_tasks WHERE flag == " + FLAG_COMPLETE;
				var selStatement = this.createStatement(selSql);
				while (selStatement.executeStep()) {
					var id = selStatement.getInt32(0);
					var title = selStatement.getUTF8String(1);
					var duetime = selStatement.getInt64(2);

					var task = new DotoTask(id);
					task.title = title;
					task.due = new Date(duetime);
					this.completedTasks.push(task);
				}
			}
			
			this.sortTask(this.tasks);
			this.sortTask(this.completedTasks, true);
			this.setTimer();
			this.dailyAlert();
		}
		gDotoStarted = true;
	},
	
	setTimer: function(){
		if (this.timer == null) {
			this.timer = Components.classes["@mozilla.org/timer;1"]
			       .createInstance(Components.interfaces.nsITimer);
		} else {
			this.timer.cancel();
		}
		
		var self = this;
		var timerCallback = { notify: function(timer) {self.timerFired(); } };
		
		for (var i = 0; i < this.tasks.length; i+=1) {
			var task = this.tasks[i];
			var now = new Date();
			if (task.due.getTime() === 0)
				break;

			if (task.due.getTime() > now.getTime() && !task.notified) {
				var diff = task.due.getTime() - now.getTime() - 5 * 60 * 1000;
				if (diff < 0) diff = 0;
				this.timer.initWithCallback(timerCallback, diff, 
						Components.interfaces.nsITimer.TYPE_ONE_SHOT);
				break;
			}
		}
	},
	
	timerFired: function(){
		for (var i = 0; i < this.tasks.length; i += 1) {
			var task = this.tasks[i];
			if (task.due.getTime() === 0)
				break;
			
			var now = new Date();
			if (task.due.getTime() > now.getTime() && !task.notified) {
				task.notified = true;

				// alert
				this.alertTask(task);
				break;			
			}
		}
		this.setTimer();
	},
	
	dailyAlert: function(){
		var lastalert = Application.prefs.getValue("extensions.doto.lastalert", "0");
		var lastDate = new Date(parseInt(lastalert));
		
		function isToday(date) {
			var now = new Date();
			return now.getFullYear() === date.getFullYear() &&
				now.getMonth() === date.getMonth() &&
				now.getDate() === date.getDate();
		}
		
		if (!isToday(lastDate)) {
			for (var i = 0; i < this.tasks.length; ++i) {
				var task = this.tasks[i];
				if (isToday(task.due)) {
					this.alertTask(task);
				}
			}
		}
		Application.prefs.setValue("extensions.doto.lastalert", "" + new Date().getTime());
	},
	
	alertTask: function(task){
		try {
			var alertService = Components.classes["@mozilla.org/alerts-service;1"]
					.getService(Components.interfaces.nsIAlertsService);
			var icon = "chrome://doto/content/res/timer.png";
			if (Math.random() < 0.05) {
				icon = "https://s3.amazonaws.com/twitter_production/profile_images/63437765/bighead_normal.png";
			}
			var listener = {
				observe: function(subject, topic, data){
					if (topic === "alertclickcallback") {
						var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
						                   .getService(Components.interfaces.nsIWindowMediator);
						var mainWindow = wm.getMostRecentWindow("navigator:browser");
						var gBrowser = mainWindow.getBrowser();
						gBrowser.selectedTab = gBrowser.addTab("about:todo");
					}
				},
			};
			alertService.showAlertNotification(icon,
					"Doto Tasks Alert",
					task.title + "\n\nOpen 'about:todo' in Firefox",
					true, "", listener);
		} catch(e) {
			Application.console.log("Doto: error occured when timer fired - " + e);
		}
	},
	
	sortTask: function(tasks, reverse) {
		tasks.sort(function (a, b) {
			if (a.due.getTime() == 0)
				return b.due.getTime();
			if (b.due.getTime() == 0)
				return - a.due.getTime();
			return a.due.getTime() - b.due.getTime();
		});
		
		if (reverse) {
			tasks.reverse();
		}
	},
	
	addTask: function(content, due) {
		var insertSql = "INSERT INTO doto_tasks (title, flag, duetime) VALUES (?1, ?2, ?3)";
		var insertStatement = this.createStatement(insertSql);
		insertStatement.bindUTF8StringParameter(0, content);
		insertStatement.bindInt32Parameter(1, FLAG_NEW);
		insertStatement.bindInt64Parameter(2, due.getTime());
		
		insertStatement.execute();
		insertStatement.finalize();
		var lastId = this.connection.lastInsertRowID;
		
		var task = new DotoTask(lastId);
		task.title = content;
		task.due = due;
		DotoManager.tasks.push(task);
		this.sortTask(this.tasks);
		
		this.setTimer();
	},
	
	deleteTask: function(id) {
		for (var i = 0; i < this.tasks.length; ++i) {
			if (this.tasks[i].id == id) {
				this.tasks.splice(i, 1);
				break;
			}
		}

		var deleteSql = "DELETE FROM doto_tasks WHERE id == ?1";
		var deleteStatement = this.createStatement(deleteSql);
		deleteStatement.bindInt32Parameter(0, id);
		deleteStatement.execute();
		deleteStatement.finalize();
		
		this.setTimer();
	},
	
	completeTask: function(id) {
		var now = new Date();
		for (var i = 0; i < this.tasks.length; ++i) {
			if (this.tasks[i].id == id) {
				var task = new DotoTask(id);
				task.title = this.tasks[i].title;
				task.due = now;
				this.completedTasks.push(task);
				this.sortTask(this.completedTasks, true);
				this.tasks.splice(i, 1);
				break;
			}
		}		

		var completeSql = "UPDATE doto_tasks SET flag=?1, duetime=?2 WHERE id == ?3";
		var completeStatement = this.createStatement(completeSql);
		completeStatement.bindInt32Parameter(0, FLAG_COMPLETE);
		completeStatement.bindInt64Parameter(1, now.getTime());
		completeStatement.bindInt32Parameter(2, id);
		completeStatement.execute();
		completeStatement.finalize();
		
		this.setTimer();
	},
	
	postponeTask: function(id) {
		var now = new Date();
		var newdue = null;
		for (var i = 0; i < this.tasks.length; ++i) {
			if (this.tasks[i].id == id) {
				if (this.tasks[i].due.getTime() == 0) {
					// due today;
					newdue = new Date(now.getTime() + 60 * 60 * 1000);
				} else {
					// postpone 1 day
					newdue = new Date(this.tasks[i].due.getTime() + 24 * 60 * 60 * 1000);
				}
				this.tasks[i].due = newdue;
				this.sortTask(this.tasks);
				break;
			}
		}
		
		if (newdue == null)
			return;
			
		var updateSql = "UPDATE doto_tasks SET duetime=?1 WHERE id == ?2";
		var updateStatement = this.createStatement(updateSql);
		updateStatement.bindInt64Parameter(0, newdue.getTime());
		updateStatement.bindInt32Parameter(1, id);
		updateStatement.execute();
		updateStatement.finalize();
		
		this.setTimer();
	},
	
	getTasks: function() {
		return this.tasks;
	},
	
	getCompletedTasks: function() {
		return this.completedTasks;
	},
	
	createStatement: function(sql) {
		try {
			var statement = this.connection.createStatement(sql);
			return statement;
		} catch(e) {
			throw new Error(this.connection.lastErrorString);
		}
	},
	
	openDatabase: function(file) {
		var connection = null;
		try {
			connection = storageService.openDatabase(file);
			if (file.fileSize == 0 || !connection.tableExists("doto_tasks")) {
				// init file;
				connection.executeSimpleSQL(SQLITE_SCHEMA);
				this.firstrun = true;
			}
		} catch(e) {
			Components.utils.reportError("Doto's database appears to have been corrupted - resetting it." + this.connection.lastErrorString);
			if (file.exists()) {
				file.remove(false);
			}
			connection = storageService.openDatabase(file);
			connection.executeSimpleSQL(SQLITE_SCHEMA);
			this.firstrun = true;
 		}
		return connection;
	},
	
	getVersionNumber: function() {
		var id = "doto@mijia.org";
		var extension = Application.extensions.get(id);
		if (extension && extension.version) {
			return extension.version;
		}
		return "1978.10.21";
	},
};